package com.hotdog.plugin;

import com.hotdog.plugin.pojo.ReloadPojo;
import com.hotdog.plugin.util.HotMessagesUtil;
import com.hotdog.plugin.util.ProcessUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.JavaSdkType;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.JavaModuleExternalPaths;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.task.ProjectTaskManager;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.concurrency.Promise;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @Author wangzhengnian
 * @Date 2021/3/17 10:42
 * @Version 1.0
 */
public class HotdogAction extends AnAction {


    private final static String RELOAD_LOCK = "RELOAD_LOCK";

    private final static String agentJarFileName = "agent.jar";


    @Override
    public void actionPerformed(AnActionEvent e) {
        hotReloadClass(e);
    }

    /**
     * 热部署
     *
     * @param e
     */
    private void hotReloadClass(AnActionEvent e) {

        //编译
        Project project = e.getProject();
        if (null == project) {
            HotMessagesUtil.showError("未找到需要更新的项目");
            return;
        }
        PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
        if (null == psiFile) {
            HotMessagesUtil.showError("未找到需要更新的文件");
            return;
        }
        VirtualFile virtualFile = psiFile.getVirtualFile();
        final Long afterUpdateTime = System.currentTimeMillis();
        //编译java

        Promise<ProjectTaskManager.Result> resultPromise = ProjectTaskManager.getInstance(project).compile(virtualFile);
        resultPromise.onSuccess(result -> {

            //填充数据
            ReloadPojo pojo = fillPojo(e);
            //编译成功后加载任务
            if (resultPromise.getState() == Promise.State.SUCCEEDED) {
                reloadClass(pojo, afterUpdateTime);
            }
        });
    }

    /**
     * 填充热更新需要的数据
     *
     * @param e
     * @return
     */
    private ReloadPojo fillPojo(AnActionEvent e) {
        Project project = e.getProject();
        if (Objects.isNull(project)) {
            return null;
        }
        PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
        PsiJavaFile psiClass = null;
        try {
            psiClass = (PsiJavaFile) psiFile;
        } catch (Exception exception) {
            exception.printStackTrace();
            HotMessagesUtil.showError("请选择类");
        }
        String moduleName = e.getData(LangDataKeys.MODULE).getName();
        Sdk sdk = ProjectRootManager.getInstance(project).getProjectSdk();
        String javaBinPath = null;
        if (Objects.nonNull(sdk)) {
            javaBinPath = ((JavaSdkType) sdk.getSdkType()).getBinPath(sdk);
        }
        String packageName = psiClass.getPackageName();
        String className = psiClass.getName().split("\\.")[0];
        String projectPath = project.getBasePath();
        String javaPath = psiClass.getVirtualFile().getPath();
        ReloadPojo pojo = new ReloadPojo();
        pojo.setJdkBinPath(javaBinPath);
        pojo.setPackageName(packageName);
        pojo.setClassName(className);
        pojo.setProjectPath(projectPath);
        pojo.setJavaPath(javaPath);
        pojo.setClassName(getClassNameByJavaPath(javaPath));
        pojo.setJavaFileName(psiClass.getName());
        pojo.setProjectJavaRoot(getProjectJavaRoot(pojo));
        pojo.setModuleName(moduleName);
        StringBuilder stringBuilder = new StringBuilder();
        if (StringUtils.isNotBlank(packageName)) {
            String[] path = packageName.split("\\.");
            for (String s : path) {
                stringBuilder.append("/").append(s);
            }
        }
        stringBuilder.append("/").append(pojo.getClassName()).append(".class");
        CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(e.getData(LangDataKeys.MODULE));
        String compilePath = compilerModuleExtension.getCompilerOutputPath().getPath();
        String classesPath = compilePath + stringBuilder;
        File file = new File(classesPath);
        if (!file.exists()) {
            HotMessagesUtil.showError("未找到class");
        }
        pojo.setClassPath(classesPath);
        Module[] modules = ModuleManager.getInstance(project).getModules();
        List<String> sourcePaths = new ArrayList<>(modules.length);
        for (Module module : modules) {
            VirtualFile[] virtualFiles = ModuleRootManager.getInstance(module).getSourceRoots();
            for (VirtualFile virtualFile : virtualFiles) {
                sourcePaths.add(virtualFile.getPath());
            }
        }
        pojo.setSourcePaths(sourcePaths);
        return pojo;
    }

    /**
     * 获取项目java文件的根目录
     *
     * @param pojo
     * @return
     */
    private String getProjectJavaRoot(ReloadPojo pojo) {
        String[] packageSplits = pojo.getPackageName().split("\\.");
        StringBuilder path = new StringBuilder();
        for (String split : packageSplits) {
            if (path.length() <= 0) {
                path.append(split);
            } else {
                path.append("/").append(split);
            }
        }
        if (path.length() <= 0) {
            return "";
        }
        String temp = path.toString() + "/" + pojo.getJavaFileName();
        return pojo.getJavaPath().replaceAll(temp, "");
    }

    /**
     * 热更新class
     *
     * @param pojo
     */
    private void reloadClass(ReloadPojo pojo, Long afterUpdateTime) {
        if (pojo.getClassPath() == null) {
            HotMessagesUtil.showError("未到找class");
            return;
        }
        try {
            List<String> pids = getPid(pojo);
            if (CollectionUtils.isNotEmpty(pids)) {
                for (String pid : pids) {
                    attach(pojo.getClassPath(), pojo.getPackageName() + "." + pojo.getClassName(), pid);
                }
                HotMessagesUtil.showSuccess("替换class成功");
            } else {
                HotMessagesUtil.showError("项目未启动");
            }
        } catch (Exception e) {
            e.printStackTrace();
            HotMessagesUtil.showError("替换class失败");
        }
    }


    private static String getClassNameByClassPath(String javaPath) {
        String[] splits = javaPath.split("/|\\\\");
        for (String split : splits) {
            if (split.endsWith(".class")) {
                return split.split("\\.")[0];
            }
        }
        return null;
    }


    private void attach(String classPath, String className, String pid) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        VirtualMachine vm = VirtualMachine.attach(pid);
        // 运行最终 AgentMain 中方法
        try {
            String agentPath = getAgentPath();
            if (null == agentPath) {
                throw new RuntimeException("未获取到agent.jar");
            }
            vm.loadAgent(agentPath, classPath + "," + className);
        } catch (AgentLoadException e) {
            if (e.getMessage().equals("0")) {
                return;
            }
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取agent包路径
     *
     * @return
     */
    private String getAgentPath() {
        String agentParentFilePath = System.getProperties().get("user.home") + "/.hotdog";
        File agentParentFile = new File(agentParentFilePath);
        if (!agentParentFile.exists()) {
            if (!agentParentFile.mkdirs()) {
                return null;
            }
        }
        String agentJarPath = agentParentFilePath + "/" + agentJarFileName;
        if (new File(agentJarPath).exists()) {
            return agentJarPath;
        }
        InputStream inputStream = HotdogAction.class.getClassLoader().getResourceAsStream(agentJarFileName);
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(agentJarPath);
            FileUtil.copy(inputStream, fileOutputStream);
            return agentJarPath;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (null != fileOutputStream) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
//
//    /**
//     * 订阅文件修改监听(暂无用)
//     *
//     * @param project
//     */
//    public void subscribeVfsChangeListen(Project project, AtomicReference<ReloadPojo> reloadPojoAtomicReference) {
//        if (null != bulkFileListener) {
//            return;
//        }
//        synchronized (HotdogAction.class) {
//            if (null != bulkFileListener) {
//                return;
//            }
//            //监听文件修改
//            project.getMessageBus().connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
//                @Override
//                public void after(@NotNull List<? extends VFileEvent> events) {
//                    ReloadPojo vfsChangeListenPojo = reloadPojoAtomicReference.get();
//                    if (null == vfsChangeListenPojo) {
//                        return;
//                    }
//                    for (VFileEvent event : events) {
//                        String path = event.getPath();
//                        if (!path.startsWith(vfsChangeListenPojo.getProjectPath())) {
//                            continue;
//                        }
//                        if (!path.endsWith(".class")) {
//                            continue;
//                        }
//                        String className = getClassNameByClassPath(path);
//                        if (!vfsChangeListenPojo.getClassName().equals(className)) {
//                            continue;
//                        }
//                        synchronized (RELOAD_LOCK) {
//                            if (null == reloadPojoAtomicReference.get()) {
//                                continue;
//                            }
//                            try {
//                                List<String> pids = getPid(reloadPojoAtomicReference.get());
//                                if (CollectionUtils.isNotEmpty(pids)) {
//                                    for (String pid : pids) {
//                                        attach(path, pid);
//                                    }
//                                    HotMessagesUtil.showSuccess("替换class成功");
//                                } else {
//                                    HotMessagesUtil.showSuccess("项目未启动");
//                                }
//                            } catch (Exception e) {
//                                e.printStackTrace();
//                                HotMessagesUtil.showError("替换class失败");
//                            }
//                            reloadPojoAtomicReference.set(null);
//                        }
//                    }
//                }
//
//
//            });
//        }
//    }

    private static List<String> getPid(ReloadPojo pojo) {
        Map<String, String> allPid = ProcessUtil.getAllJavaPid(pojo.getJdkBinPath());
        if (MapUtils.isEmpty(allPid)) {
            return null;
        }
        List<String> pids = new ArrayList<>(allPid.size());
        for (Map.Entry<String, String> obj : allPid.entrySet()) {
            String[] splits = obj.getValue().split("\\.");
            if (splits.length <= 0) {
                continue;
            }
            StringBuilder stringBuilder = new StringBuilder("");
            for (String split : splits) {
                stringBuilder.append("/").append(split);
            }
            String filePath = stringBuilder.toString() + ".java";
            for (String sourcePath : pojo.getSourcePaths()) {
                File file = new File(sourcePath + filePath);
                if (file.exists()) {
                    pids.add(obj.getKey());
                }
            }
        }
        if (pids.size() > 0) {
            return pids;
        }
        return null;
    }


    private static String getClassNameByJavaPath(String javaPath) {
        String[] splits = javaPath.split("/|\\\\");
        for (String split : splits) {
            if (split.endsWith(".java")) {
                return split.split("\\.")[0];
            }
        }
        return null;
    }


}
